/**
 * Copyright 2018 人人开源 http://www.renren.io
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.qdone.utils;

import com.qdone.entity.ColumnEntity;
import com.qdone.entity.TableEntity;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.velocity.app.Velocity;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.ObjectUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成器   工具类
 * 
 * @author chenshun
 * @email sunlightcs@gmail.com
 * @date 2016年12月19日 下午11:40:24
 */
public class GenUtils {

	public static List<String> getTemplates(){
		List<String> templates = new ArrayList<String>();
		templates.add("controller.ftl");
		templates.add("service.ftl");
		templates.add("serviceImpl.ftl");
		/*templates.add("dao.ftl");*/
		templates.add("entity.ftl");
		templates.add("mapper.ftl");
		templates.add("list.html.ftl");
		templates.add("insert.html.ftl");
		templates.add("update.html.ftl");
		return templates;
	}
	
	/**
	 * 生成代码
	 */
	public static void generatorCode(freemarker.template.Configuration freemaker,Map<String, String> table,
			List<Map<String, String>> columns, ZipOutputStream zip){
		//配置信息
		Configuration config = getConfig();
		boolean hasBigDecimal = false;
		boolean hasDate = false;
		//表信息
		TableEntity tableEntity = new TableEntity();
		tableEntity.setTableName(table.get("tableName"));
		tableEntity.setComments(table.get("tableComment"));
		//表名转换成Java类名
		String className = tableToJava(tableEntity.getTableName(), config.getString("tablePrefix"));
		tableEntity.setClassName(className);
		tableEntity.setClassname(StringUtils.uncapitalize(className));
		
		//列信息
		List<ColumnEntity> columsList = new ArrayList<>();
		for(Map<String, String> column : columns){
			ColumnEntity columnEntity = new ColumnEntity();
			columnEntity.setColumnName(column.get("columnName"));
			//数据库列类型
			columnEntity.setDatatype(column.get("dataType"));
			//数据库列类型大写
			columnEntity.setDataType(getJdbcType(column.get("dataType")));
			columnEntity.setComments(column.get("columnComment"));
			columnEntity.setExtra(column.get("extra"));
			//最大长度
			columnEntity.setMaxLength(ObjectUtils.isEmpty(column.get("maxLength"))?0:Long.parseLong(String.valueOf(column.get("maxLength"))));
			if(column.get("nullAble").toString().toUpperCase().equals("NO")){
				columnEntity.setNullAble(true);
			}else{
				columnEntity.setNullAble(false);
			}
			//列位置
			columnEntity.setPosition(ObjectUtils.isEmpty(column.get("position"))?0:Long.parseLong(String.valueOf(column.get("position"))));
			//默认值
			if(StringUtils.isNotEmpty(column.get("defaultValue"))){
				String defaultValue=column.get("defaultValue");
				if(getJdbcType(column.get("dataType")).equals("BIT")){//Boolean特殊处理
					defaultValue=defaultValue.equals("b'1'")?"true":"false";
				}
				columnEntity.setDefaultValue(defaultValue);
			}
			//列名转换成Java属性名
			String attrName = columnToJava(columnEntity.getColumnName());
			columnEntity.setAttrName(attrName);
			columnEntity.setAttrname(StringUtils.uncapitalize(attrName));
			
			//列的数据类型，转换成Java类型
			String attrType = config.getString(columnEntity.getDatatype(), "unknowType");
			columnEntity.setAttrType(attrType);
			columnEntity.setPropType(getJavaTypeAbs(attrType));
			if (!hasBigDecimal && attrType.equals("BigDecimal" )) {
				hasBigDecimal = true;
			}
			if (!hasDate && attrType.equals("Date" )) {
				hasDate = true;
			}
			//是否主键
			if("PRI".equalsIgnoreCase(column.get("columnKey")) && tableEntity.getPk() == null){
				tableEntity.setPk(columnEntity);
			}

			columsList.add(columnEntity);
		}

		tableEntity.setColumns(columsList);
		
		//没主键，则第一个字段为主键
		if(tableEntity.getPk() == null){
			tableEntity.setPk(tableEntity.getColumns().get(0));
		}

		String mainPath = config.getString("mainPath" );
		mainPath = StringUtils.isBlank(mainPath) ? "com.qdone" : mainPath;
		
		//封装模板数据
		Map<String, Object> map = new HashMap<>();
		map.put("tableName", tableEntity.getTableName().toUpperCase());
		map.put("comments", tableEntity.getComments());
		map.put("pk", tableEntity.getPk());
		map.put("className", tableEntity.getClassName());
		map.put("classname", tableEntity.getClassname());
		map.put("pathName", tableEntity.getClassname().toLowerCase());
		map.put("columns", tableEntity.getColumns());
		map.put("hasBigDecimal", hasBigDecimal);
		map.put("hasDate", hasDate);
		map.put("mainPath", mainPath);
		map.put("package", config.getString("package" ));
		map.put("moduleName", config.getString("moduleName" ));
		map.put("author", config.getString("author"));
		map.put("email", config.getString("email"));
		map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN));

        //获取模板列表
		List<String> templates = getTemplates();
		for(String template : templates){
			//渲染模板
			try {
				Template templete=freemaker.getTemplate(template);
				//可以针对不同模板数据特殊处理
				//添加页面还有更新页面比较特殊，页面循环复杂这里直接自定义
				buildPageElements(template,map,tableEntity);
				String content = FreeMarkerTemplateUtils.processTemplateIntoString(templete, map);
				//添加到zip
				zip.putNextEntry(new ZipEntry(getFileName(template, tableEntity.getClassName(), config.getString("package"), config.getString("moduleName"))));
				IOUtils.write(content, zip, "UTF-8");
				/*IOUtils.closeQuietly(content);*/
				zip.closeEntry();
			}catch (TemplateException e) {
				throw new RRException("渲染模板失败，表名：" + tableEntity.getTableName(), e);
			} catch (IOException e) {
				throw new RRException("渲染模板失败，表名：" + tableEntity.getTableName(), e);
			}
		}
	}


	/**
	 * 根据数据库类型
	 * 找到对应的jdbcType
	 * @param columnType
	 * @return jdbcType
	 */
	public static String getJdbcType(String columnType){
		String[] blobArr=new String[]{"TINYBLOB","BLOB","MEDIUMBLOB","LONGBLOB"};
		String[] intArr=new String[]{"INTEGER","INT"};
		String[] textArr=new String[]{"TEXT","MEDIUMTEXT","LONGTEXT"};
		String[] timeArr=new String[]{"TIMESTAMP","DATETIME"};
		String[] charArr=new String[]{"VARCHAR","TINYTEXT"};
		if(Arrays.asList(blobArr).contains(columnType.toUpperCase())){
			columnType="BLOB";
		}else if(Arrays.asList(intArr).contains(columnType.toUpperCase())){
			columnType="INTEGER";
		}else if(Arrays.asList(textArr).contains(columnType.toUpperCase())){
			columnType="LONGVARCHAR";
		}else if(Arrays.asList(timeArr).contains(columnType.toUpperCase())){
			columnType="TIMESTAMP";
		}else if(Arrays.asList(charArr).contains(columnType.toUpperCase())){
			columnType="VARCHAR";
		}
		return columnType.toUpperCase();
	}

	/**
	 * 针对页面的特殊组件拼接复杂这里直接匹配写死
	 * 目前只针对 insert，update特殊处理
	 * @param template
	 * @param map
	 * @return
	 */
	public static Map<String, Object> buildPageElements(String template, Map<String, Object> map,TableEntity tableEntity){
		String classname=tableEntity.getClassname();
		int type=0;//生成表格是否含value，0表示insert 1表示update
		//添加表格
		StringBuffer searchAreaParam = new StringBuffer(1024);
		/*绘制添加表格，默认每行2列*/
		ArrayList<ColumnEntity> arr= (ArrayList<ColumnEntity>) tableEntity.getColumns();
		ColumnEntity pk=tableEntity.getPk();
      if(template.equals("insert.html.ftl")){
		  //过滤掉主键列
		  CollectionUtils.filter(arr, new Predicate(){
			  @Override
			  public boolean evaluate(Object arg) {
				  ColumnEntity v = (ColumnEntity)arg;
				  return StringUtils.isNotBlank(v.getAttrname())&&StringUtils.isNotBlank(pk.getAttrname()) && !pk.getAttrname().equals(v.getAttrname());
			  }
		  });
		  int rowCloumSize=2;//每行两列显示
		  if(arr.size()%rowCloumSize==0){//刚好整数倍，分拨次生成多行
			  for (int i = 0; i <arr.size()/rowCloumSize; i++) {//共计多少行，field分多少组执行
				  createRow(searchAreaParam,arr.subList(i*rowCloumSize, (i+1)*rowCloumSize),type,classname);
			  }
		  } else {
			  if (arr.size() / rowCloumSize == 0) {// 不够rowCloumSize列，直接全部字段生成一行
				  createRow(searchAreaParam, arr,type,classname);
			  } else {
				  for (int i = 0; i < arr.size() / rowCloumSize + 1; i++) {// 不是整数倍，分拨次生成多行，最后一行直接生成
					  if (i == arr.size() / rowCloumSize) {
						  createRow(searchAreaParam, arr.subList(i * rowCloumSize, arr.size()),type,classname);
					  } else {
						  createRow(searchAreaParam, arr.subList(i * rowCloumSize, (i + 1) * rowCloumSize),type,classname);
					  }
				  }
			  }
		  }
		  map.put("insertFormParam",searchAreaParam.toString());
	  } else if(template.equals("update.html.ftl")){
		   //过滤掉主键列
		   CollectionUtils.filter(arr, new Predicate(){
			  @Override
			  public boolean evaluate(Object arg) {
				  ColumnEntity v = (ColumnEntity)arg;
				  return StringUtils.isNotBlank(v.getAttrname())&&StringUtils.isNotBlank(pk.getAttrname()) && !pk.getAttrname().equals(v.getAttrname());
			  }
		   });
      	    type=1;//更新
			int rowCloumSize=2;//每行两列显示
			if(arr.size()%rowCloumSize==0){//刚好整数倍，分拨次生成多行
				for (int i = 0; i <arr.size()/rowCloumSize; i++) {//共计多少行，field分多少组执行
					createRow(searchAreaParam,arr.subList(i*rowCloumSize, (i+1)*rowCloumSize),type,classname);
				}
			} else {
				if (arr.size() / rowCloumSize == 0) {// 不够rowCloumSize列，直接全部字段生成一行
					createRow(searchAreaParam, arr,type,classname);
				} else {
					for (int i = 0; i < arr.size() / rowCloumSize + 1; i++) {// 不是整数倍，分拨次生成多行，最后一行直接生成
						if (i == arr.size() / rowCloumSize) {
							createRow(searchAreaParam, arr.subList(i * rowCloumSize, arr.size()),type,classname);
						} else {
							createRow(searchAreaParam, arr.subList(i * rowCloumSize, (i + 1) * rowCloumSize),type,classname);
						}
					}
				}
			}
			map.put("updateFormParam",searchAreaParam.toString());
		}
      return map;
	}


	/**
	 * 获取数据库字段类型对应java的类型
	 * eg：varchar  String
	 * return java.lang.String
	 * @param atrrType
	 * @return
	 */
	public static String getJavaTypeAbs(String atrrType){
		String atrrTypeAbs="java.lang.String";
        if(StringUtils.isEmpty(atrrType)){
        	return atrrTypeAbs;
		}
        if(atrrType.equals("Integer")){
			atrrTypeAbs="java.lang.Integer";
		}else if(atrrType.equals("Long")){
			atrrTypeAbs="java.lang.Long";
		}else if(atrrType.equals("Float")){
			atrrTypeAbs="java.lang.Float";
		}else if(atrrType.equals("Double")){
			atrrTypeAbs="java.lang.Double";
		}else if(atrrType.equals("BigDecimal")){
			atrrTypeAbs="java.math.BigDecimal";
		}else if(atrrType.equals("Boolean")){
			atrrTypeAbs="java.lang.Boolean";
		}else if(atrrType.equals("String")){
			atrrTypeAbs="java.lang.String";
		}else if(atrrType.equals("Date")){
			atrrTypeAbs="java.util.Date";
		}
		return  atrrTypeAbs;
	}
	/**
	 * 列名转换成Java属性名
	 */
	public static String columnToJava(String columnName) {
		return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
	}
	
	/**
	 * 表名转换成Java类名
	 */
	public static String tableToJava(String tableName, String tablePrefix) {
		if(StringUtils.isNotBlank(tablePrefix)){
			tableName = tableName.replace(tablePrefix, "");
		}
		return columnToJava(tableName);
	}
	
	/**
	 * 获取配置信息
	 */
	public static Configuration getConfig(){
		try {
			return new PropertiesConfiguration("generator.properties");
		} catch (ConfigurationException e) {
			throw new RRException("获取配置文件失败，", e);
		}
	}

	/**
	 * 获取文件名
	 */
	public static String getFileName(String template, String className, String packageName, String moduleName) {
		String packagePath = "main" + File.separator + "java" + File.separator;
		if (StringUtils.isNotBlank(packageName)) {
			packagePath += packageName.replace(".", File.separator) + File.separator + moduleName + File.separator;
		}
		if (template.contains("controller.ftl" )) {
			return packagePath + "controller" + File.separator + className + "Controller.java";
		}
		if (template.contains("service.ftl" )) {
			return packagePath + "service" + File.separator + className + "Service.java";
		}
		if (template.contains("serviceImpl.ftl" )) {
			return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
		}
		if (template.contains("entity.ftl" )) {
			return packagePath + "model" + File.separator + className + ".java";
		}
		if (template.contains("dao.ftl" )) {
			return packagePath + "dao" + File.separator + className + "Dao.java";
		}
		if (template.contains("mapper.ftl" )) {
			return packagePath + "mapper" + File.separator + className + ".xml";
		}
		if (template.contains("list.html.ftl" )) {
			return "main" + File.separator + "resources" + File.separator + "page" + File.separator
					+ "view" + File.separator + className.toLowerCase() + File.separator + "select"+className + ".html";
		}
		if (template.contains("insert.html.ftl" )) {
			return "main" + File.separator + "resources" + File.separator + "page" + File.separator
					+ "view" + File.separator + className.toLowerCase() + File.separator + "insert"+className + ".html";
		}
		if (template.contains("update.html.ftl" )) {
			return "main" + File.separator + "resources" + File.separator + "page" + File.separator
					+ "view" + File.separator + className.toLowerCase() + File.separator + "update"+className + ".html";
		}
		return null;
	}
	/**
	 * 创建某行
	 */
	public static  String createRow(StringBuffer searchAreaParam,List<ColumnEntity> arr,int type,String classname){
		searchAreaParam.append("             <tr>\n");
		for (int i = 0; i < arr.size(); i++) {
			createOneProperty(searchAreaParam, arr.get(i),type,classname);
		}
		searchAreaParam.append("             </tr>\n");
		return searchAreaParam.toString();
	}
	/**
	 * 创建某列字段
	 * @param searchAreaParam
	 * @param field
	 * @return
	 * updateby 付为地 2017-07-04 添加表格字段的长度控制,针对数据库varchar比较长的字段做处理,生成textarea方式
	 *          数据库可为空的,默认全部都是非必填,不能为空这里全部都是必填
	 */
	public static  String createOneProperty(StringBuffer searchAreaParam,ColumnEntity field,int type,String classname){
		//form表单的标题
		searchAreaParam.append((new StringBuilder("                <th>")).append(field.getComments()).append("</th>\n").toString());
		String varStr="";
		String val="";
		if(type==1){
			varStr="value='<#if "+classname+"."+field.getAttrname()+" ??>${"+classname+"."+field.getAttrname()+"}</#if>'";
		    val="<#if "+classname+"."+field.getAttrname()+" ??>${"+classname+"."+field.getAttrname()+"}</#if>";
		}
		//form表单的内容，生成对应easyui控件
		//不可为空
		if(field.getNullAble()){
					//日期类型
					if(field.getAttrType().equals("Date")){//日期类型
						if(type==1){//update
							varStr="value='<#if "+classname+"."+field.getAttrname()+" ??>${"+classname+"."+field.getAttrname()+"?string(\"yyyy-MM-dd HH:mm:ss\")}</#if>'";
						}
						searchAreaParam.append("                <td><input id='"+field.getAttrname()+"' "+varStr+"  name='"+field.getAttrname()+"' readonly='true' onclick=\"laydate({istime: true, format: 'YYYY-MM-DD hh:mm:ss'})\"/></td>\n");
					}
					//number类型
					else if(field.getAttrType().equals("Integer")||field.getAttrType().equals("double")||field.getAttrType().equals("BigDecimal")){
						searchAreaParam.append("                <td><input id='"+field.getAttrname()+"' "+varStr+" name='"+field.getAttrname()+"' class='required number'  /></td>\n ");
					}
					//字符串类型
					else{
						Long max=field.getMaxLength()==0?1:field.getMaxLength();
						//超过100生成textarea
						if(field.getMaxLength()>=100){
							if(type==1){//update
								searchAreaParam.append("                <td><textarea id='"+field.getAttrname()+"' "+varStr+" name='"+field.getAttrname()+"' class='{required:true,maxlength:"+max+"}' row='2'/>"+val+"</textarea></td>\n");
							}else{//insert
								searchAreaParam.append("                <td><textarea id='"+field.getAttrname()+"' "+varStr+" name='"+field.getAttrname()+"' class='{required:true,maxlength:"+max+"}' row='2'/></textarea></td>\n");
							}
						 }
						else{
							searchAreaParam.append("                <td><input id='"+field.getAttrname()+"' "+varStr+" name='"+field.getAttrname()+"' class='{required:true,maxlength:"+max+"}'/></td>\n");
						}
					}
		  }else{//可为空
						//日期类型
						if(field.getAttrType().equals("Date")){//日期类型
							if(type==1){//update
								varStr="value='<#if "+classname+"."+field.getAttrname()+" ??>${"+classname+"."+field.getAttrname()+"?string(\"yyyy-MM-dd HH:mm:ss\")}</#if>'";
							}
							searchAreaParam.append("                <td><input id='"+field.getAttrname()+"' "+varStr+" name='"+field.getAttrname()+"' readonly='true' onclick=\"laydate({istime: true, format: 'YYYY-MM-DD hh:mm:ss'})\"/></td>\n");
						}
						//number类型
						else if(field.getAttrType().equals("Integer")||field.getAttrType().equals("Float")||field.getAttrType().equals("Double")||field.getAttrType().equals("BigDecimal")){
							searchAreaParam.append("                <td><input id='"+field.getAttrname()+"' "+varStr+"  name='"+field.getAttrname()+"'  /></td>\n ");
						}
						//字符串类型
						else{
							if(field.getMaxLength()>=100){//超过100生成textarea
								if(type==1){//update
									searchAreaParam.append("                <td><textarea id='"+field.getAttrname()+"' "+varStr+" name='"+field.getAttrname()+"'  row='2'/>"+val+"</textarea></td>\n");
								}else{
									searchAreaParam.append("                <td><textarea id='"+field.getAttrname()+"' "+varStr+" name='"+field.getAttrname()+"'  row='2'/></textarea></td>\n");
								}
							}else{
								searchAreaParam.append("                <td><input id='"+field.getAttrname()+"' "+varStr+" name='"+field.getAttrname()+"'/></td>\n");
							 }
						}
					}
					return searchAreaParam.toString();
		}

}
